package com.vincent.template.service.logic.impl;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.vincent.template.commons.constanst.DepartmentType;
import com.vincent.template.commons.exception.Vct520Exception;
import com.vincent.template.domain.BizDepartmentPo;
import com.vincent.template.dto.biz.DepartmentDto;
import com.vincent.template.dto.biz.DepartmentTreeDto;
import com.vincent.template.repository.BizDepartmentRepository;
import com.vincent.template.service.logic.DepartmentService;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * @Author Vincent Wang
 * @Date 2020/10/19 14:06
 */
@Service
public class DepartmentServiceImpl implements DepartmentService {

    private static final long ROOT_PARENT_NO = -1;

    @Autowired
    private BizDepartmentRepository departmentRepository;

    @Override
    public Long insert(DepartmentDto dto) {
        if (dto.getParentNo().equals(ROOT_PARENT_NO)) {
            BizDepartmentPo department = departmentRepository.findByDepartmentNo(0);
            Vct520Exception.throwIfTrue(department != null, "There is and can only be one root-level department!");
        } else {
            BizDepartmentPo parentDepartment = departmentRepository.findByDepartmentNo(dto.getParentNo());
            Vct520Exception.throwIfEmpty(parentDepartment, "parent department doesn't exist!");
            if (DepartmentType.COMPANY.name().equals(dto.getDepartmentType())) {
                dto.setDepartmentFullName(dto.getDepartmentName());
            } else {
                dto.setDepartmentFullName(parentDepartment.getDepartmentFullName() + "/" + dto.getDepartmentName());
            }
        }
        BizDepartmentPo department = new BizDepartmentPo();
        BeanUtils.copyProperties(dto, department);
        Long departmentNo = departmentRepository.getMaxDepartmentNo() + 1;
        department.setDepartmentNo(departmentNo);
        return departmentRepository.save(department).getDepartmentNo();
    }

    @Override
    public Boolean update(Long departmentNo, DepartmentDto dto) {
        BizDepartmentPo department = departmentRepository.findByDepartmentNo(departmentNo);
        Vct520Exception.throwIfEmpty(department, "department doesn't exist!");
        Long parentNo = dto.getParentNo();
        Vct520Exception.throwIfTrue(parentNo.equals(ROOT_PARENT_NO), "There is and can only be one root-level department!");
        BizDepartmentPo parentDepartment = departmentRepository.findByDepartmentNo(parentNo);
        Vct520Exception.throwIfEmpty(parentDepartment, "parent department doesn't exist!");
        Vct520Exception.throwIfTrue(!parentNo.equals(department.getParentNo()) &&
                isIllegalParent(departmentNo, parentNo), "parentNo is illegal!");
        departmentRepository.saveAll(getAffectedDepartments(parentDepartment.getDepartmentFullName(), dto, department));
        return true;
    }

    @Override
    public Boolean move(Long departmentNo, Long parentNo) {
        Vct520Exception.throwIfTrue(departmentNo.equals(ROOT_PARENT_NO), "root-level department can not be moved!");
        Vct520Exception.throwIfTrue(departmentNo.equals(parentNo), "parentNo can not be same as departmentNo!");
        BizDepartmentPo department = departmentRepository.findByDepartmentNo(departmentNo);
        Vct520Exception.throwIfEmpty(department, "department doesn't exist!");
        Vct520Exception.throwIfTrue(parentNo.equals(department.getParentNo()), "parentNo doesn't change!");
        BizDepartmentPo parentDepartment = departmentRepository.findByDepartmentNo(parentNo);
        Vct520Exception.throwIfEmpty(parentDepartment, "parentNo is illegal!");
        Vct520Exception.throwIfTrue(isIllegalParent(departmentNo, parentNo), "parentNo is illegal!");
        department.setParentNo(parentNo);
        if (DepartmentType.COMPANY.name().equals(department.getDepartmentType())) {
            departmentRepository.save(department);
            return true;
        }
        department.setDepartmentFullName(parentDepartment.getDepartmentFullName() + "/" + department.getDepartmentName());
        Map<Long, BizDepartmentPo> affectedDepartmentMap = Maps.newHashMap();
        affectedDepartmentMap.put(department.getDepartmentNo(), department);
        generateAffectedDepartmentMap(department.getDepartmentNo(), affectedDepartmentMap);
        departmentRepository.saveAll(affectedDepartmentMap.values());
        return true;
    }

    @Override
    public Boolean delete(Long departmentNo, Boolean removeSubs) {
        Vct520Exception.throwIfTrue(departmentNo.equals(ROOT_PARENT_NO), "root-level department can not be deleted!");
        BizDepartmentPo department = departmentRepository.findByDepartmentNo(departmentNo);
        Vct520Exception.throwIfEmpty(department, "department doesn't exist!");
        Map<Long, BizDepartmentPo> affectedDepartmentMap = Maps.newHashMap();
        affectedDepartmentMap.put(department.getDepartmentNo(), department);
        generateAffectedDepartmentMap(departmentNo, affectedDepartmentMap);
        // 如果size为1，说明没有子部门，直接删除当前部门后返回
        if (affectedDepartmentMap.size() == 1) {
            department.setDeleted(true);
            departmentRepository.save(department);
            return true;
        }
        Vct520Exception.throwIfTrue(!removeSubs, "Sub departments exist and you didn't choose to remove them at the same time, please move or remove them first!");
        // TODO: 2020/10/20 后续需要判断当时是否存在与这些部门绑定的员工，如果存在则不能直接删除部门
        affectedDepartmentMap.values().forEach(dept -> dept.setDeleted(true));
        departmentRepository.saveAll(affectedDepartmentMap.values());
        return true;
    }

    @Override
    public DepartmentDto findDepartment(Long departmentNo) {
        BizDepartmentPo department = departmentRepository.findByDepartmentNo(departmentNo);
        Vct520Exception.throwIfEmpty(department, "department doesn't exist!");
        DepartmentDto dto = new DepartmentDto();
        BeanUtils.copyProperties(department, dto);
        return dto;
    }

    @Override
    public List<DepartmentDto> findAll() {
        List<BizDepartmentPo> departments = departmentRepository.findAll(Sort.by("createTime"));
        if (CollectionUtils.isEmpty(departments)) {
            return Lists.newArrayList();
        }
        return departments.stream().filter(po -> !po.isDeleted()).map(po -> {
            DepartmentDto dto = new DepartmentDto();
            BeanUtils.copyProperties(po, dto);
            return dto;
        }).collect(Collectors.toList());
    }

    @Override
    public DepartmentTreeDto findDepartmentTree(Long departmentNo) {
        BizDepartmentPo department = departmentRepository.findByDepartmentNo(departmentNo);
        if (department == null || department.isDeleted()) {
            return null;
        }
        DepartmentTreeDto tree = new DepartmentTreeDto();
        generateDepartmentTree(department, tree);
        return tree;
    }

    @Override
    public Boolean syncDepartmentFullName(Long departmentNo) {
        BizDepartmentPo department = departmentRepository.findByDepartmentNo(departmentNo);
        Vct520Exception.throwIfEmpty(department, "department doesn't exist!");
        Vct520Exception.throwIfTrue(departmentNo != 0 && StringUtils.isBlank(department.getDepartmentFullName()),
                "The first synchronization must start from the root departmentNo: 0");
        if (DepartmentType.COMPANY.name().equals(department.getDepartmentType())) {
            department.setDepartmentFullName(department.getDepartmentName());
        }
        Map<Long, BizDepartmentPo> affectedDepartmentMap = Maps.newHashMap();
        affectedDepartmentMap.put(department.getDepartmentNo(), department);
        generateAffectedDepartmentMap(departmentNo, affectedDepartmentMap);
        departmentRepository.saveAll(affectedDepartmentMap.values());
        return true;
    }

    private List<BizDepartmentPo> getAffectedDepartments(String parentFullName,
                                                         DepartmentDto dto,
                                                         BizDepartmentPo department) {
        if (DepartmentType.COMPANY.name().equals(dto.getDepartmentType())) {
            dto.setDepartmentFullName(dto.getDepartmentName());
        } else {
            dto.setDepartmentFullName(parentFullName + "/" + dto.getDepartmentName());
        }
        boolean stopFlag = dto.getDepartmentFullName().equals(department.getDepartmentFullName());
        BeanUtils.copyProperties(dto, department, "departmentNo");
        if (stopFlag) {
            return Lists.newArrayList(department);
        }
        Map<Long, BizDepartmentPo> affectedDepartmentMap = Maps.newHashMap();
        affectedDepartmentMap.put(department.getDepartmentNo(), department);
        generateAffectedDepartmentMap(department.getDepartmentNo(), affectedDepartmentMap);
        return Lists.newArrayList(affectedDepartmentMap.values());
    }

    /**
     * 递归生成部门Map
     * @param departmentNo
     * @param affectedDepartmentMap
     */
    private void generateAffectedDepartmentMap(Long departmentNo, Map<Long, BizDepartmentPo> affectedDepartmentMap) {
        List<BizDepartmentPo> branchDepartments = departmentRepository.findByParentNo(departmentNo);
        if (CollectionUtils.isEmpty(branchDepartments)) {
            return;
        }
        branchDepartments.stream().filter(bd -> !bd.isDeleted()).forEach(bd -> {
            if (DepartmentType.COMPANY.name().equals(bd.getDepartmentType())) {
                bd.setDepartmentFullName(bd.getDepartmentName());
            } else {
                bd.setDepartmentFullName(affectedDepartmentMap.get(bd.getParentNo()).getDepartmentFullName()
                        + "/" + bd.getDepartmentName());
            }
            affectedDepartmentMap.put(bd.getDepartmentNo(), bd);
            generateAffectedDepartmentMap(bd.getDepartmentNo(), affectedDepartmentMap);
        });
    }

    /**
     * 递归生成部门树
     * @param department
     * @param tree
     */
    private void generateDepartmentTree(BizDepartmentPo department, DepartmentTreeDto tree) {
        BeanUtils.copyProperties(department, tree);
        tree.setBranches(Lists.newArrayList());
        List<BizDepartmentPo> branchDepartments = departmentRepository.findByParentNo(department.getDepartmentNo());
        if (CollectionUtils.isEmpty(branchDepartments)) {
            return;
        }
        branchDepartments.stream().filter(bd -> !bd.isDeleted()).forEach(bd -> {
            DepartmentTreeDto branch = new DepartmentTreeDto();
            tree.getBranches().add(branch);
            generateDepartmentTree(bd, branch);
        });
    }

    /**
     * 判断新的父级部门ID是否合法（儿孙不能变成老子）
     * @param departmentNo
     * @param parentNo
     * @return
     */
    private boolean isIllegalParent(Long departmentNo, Long parentNo) {
        List<BizDepartmentPo> branchDepartments = departmentRepository.findByParentNo(departmentNo);
        if (CollectionUtils.isEmpty(branchDepartments)) {
            return false;
        }
        return branchDepartments.stream().anyMatch(bd -> parentNo.equals(bd.getDepartmentNo()) ||
                isIllegalParent(bd.getDepartmentNo(), parentNo));
    }
}
